﻿using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Epi;
using CDC.SampleShell.Domain;
using CDC.SampleShell.Domain.Events;

namespace CDC.SampleShell.Applications
{
    public sealed class RecordViewModel : DynamicViewModelBase, IDisposable
    {
        private bool _isSelectedForDeletion = false;

        public bool IsLocked 
        {
            get
            {
                return Record.IsLocked;
            }
            set
            {
                Record.IsLocked = value;
                RaisePropertyChanged("IsLocked");
            }
        }

        public bool IsSelectedForDeletion
        {
            get
            {
                return _isSelectedForDeletion;
            }
            set
            {
                _isSelectedForDeletion = value;
                RaisePropertyChanged("IsSelectedForDeletion");
            }
        }

        private Record Record { get; set; }
        public Guid GlobalRecordId
        {
            get
            {
                return Record.GlobalRecordId;
            }
        }

        public event EventHandler<RecordSavedEventArgs> RecordSaved;
        public event EventHandler<EventArgs> RecordDeletionRequested;

        public RecordViewModel(Record record)
        {
            Record = record;
            Record.Locked += Record_Locked;
            Record.Unlocked += Record_Unlocked;
        }

        void Record_Unlocked(object sender, EventArgs e)
        {
            RaisePropertyChanged("IsLocked");
        }

        void Record_Locked(object sender, EventArgs e)
        {

            RaisePropertyChanged("IsLocked");
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                Record.Locked -= Record_Locked;
                Record.Unlocked -= Record_Unlocked;
            }
            // free native resources if there are any.
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            string fieldName = binder.Name;

            if (Record.ContainsField(fieldName))
            {
                result = Record.GetData(fieldName);
                return true;
            }
            else
            {
                result = null;
                return false;
            }
        }

        public override bool TrySetMember(SetMemberBinder binder, object result)
        {
            string fieldName = binder.Name;

            if (Record.ContainsField(fieldName))
            {
                Record.SetData(fieldName, result);
                return true;
            }
            else
            {
                Record.SetData(fieldName, result);
                return true;
            }
        }

        public void NotifyOnAllProperties()
        {
            foreach (string fieldName in Record.FieldNames)
            {
                RaisePropertyChanged(fieldName);
            }
        }

        public static void AddRecord(CDC.SampleShell.Domain.Project project, View form)
        {
            Epi.Windows.Enter.EnterMainForm emf = null;

            try
            {
                // ui config doesn't just take a true/false for its properties, it takes a View/boolean pair. This is done 
                // because, even when opening a single form, users may navigate to sub-forms from within that form, and 
                // different UI configurations may be necessary for different forms.
                Epi.Enter.EnterUIConfig uiConfig = new Epi.Enter.EnterUIConfig();

                // Examples:
                // --------------------------------------------------------
                // uiConfig.ShowToolbar // would hide the ENTER toolbar entirely
                // uiConfig.ShowNavButtons // would disable record navigation in ENTER's toolbar; effectively, this limits you to editing just the one record you started on
                // uiConfig.ShowNewRecordButton // hides just the new record button on the toolbar
                // uiConfig.ShowPrintButton // hides just the print button on the toolbar
                // uiConfig.ShowEditFormButton // hides the 'Edit form' button on the toolbar (this one is usually set to false by shell apps so data entry clerks can't quickly edit the form)

                emf = new Epi.Windows.Enter.EnterMainForm(project, form, uiConfig);
            }
            catch (NullReferenceException)
            {
                throw; // this generally shouldn't be needed anymore due to bug fixes in Epi Info 7.1.4.x but am keeping it here just in case
            }

            emf.RecordSaved += static_RecordSaved;
            emf.ShowDialog();
            emf.RecordSaved -= static_RecordSaved;
            emf.Dispose();
            emf = null;
        }

        private static void static_RecordSaved(object sender, SaveRecordEventArgs e)
        {
            (e.Form.Project as CDC.SampleShell.Domain.Project).Legacy_RecordAdded(e.RecordGuid); // hack!
        }

        public ICommand DeleteRecordCommand { get { return new RelayCommand(DeleteRecordExecute); } }
        private void DeleteRecordExecute()
        {
            if (RecordDeletionRequested != null)
            {
                RecordDeletionRequested(this, new EventArgs());
            }
        }

        private bool CanExecuteEditRecord()
        {
            if (IsLocked) return false;
            return true;
        }

        public ICommand EditRecordCommand { get { return new RelayCommand(EditRecordExecute, CanExecuteEditRecord); } }
        private void EditRecordExecute()
        {
            // This method calls the EnterMainForm such that we can show an Epi Info 7 data entry screen.
            // Note that we need to pass in the Project, the Form, and a UI config. The UI config is a class
            // that allows us to change some properties of how Enter looks and what buttons/controls are
            // available to the user.

            Epi.Windows.Enter.EnterMainForm emf = null;

            try
            {
                // ui config doesn't just take a true/false for its properties, it takes a View/boolean pair. This is done 
                // because, even when opening a single form, users may navigate to sub-forms from within that form, and 
                // different UI configurations may be necessary for different forms.
                Epi.Enter.EnterUIConfig uiConfig = new Epi.Enter.EnterUIConfig();

                // Examples:
                // --------------------------------------------------------
                // uiConfig.ShowToolbar // would hide the ENTER toolbar entirely
                // uiConfig.ShowNavButtons // would disable record navigation in ENTER's toolbar; effectively, this limits you to editing just the one record you started on
                // uiConfig.ShowNewRecordButton // hides just the new record button on the toolbar
                // uiConfig.ShowPrintButton // hides just the print button on the toolbar
                // uiConfig.ShowEditFormButton // hides the 'Edit form' button on the toolbar (this one is usually set to false by shell apps so data entry clerks can't quickly edit the form)

                emf = new Epi.Windows.Enter.EnterMainForm(Record.Form.Project as Epi.Project, Record.Form, uiConfig);
            }
            catch (NullReferenceException)
            {
                throw; // this generally shouldn't be needed anymore due to bug fixes in Epi Info 7.1.4.x but am keeping it here just in case
            }

            int uniqueKey = (int)(Record.GetData("UniqueKey"));

            // IMPORTANT: The event handlers here are necessary because Epi Info 7 Enter lacks a collection
            // of records that we can pull from. Enter has no concept of SampleShell's domain model. We
            // instead have to detect that a save event has occurred, and then update our own record
            // collection as appropriate by going back to the database and issuing a SELECT query. (This
            // is handled by the repository, it's not done here, but it could be done here if needed.)
            emf.LoadRecord(uniqueKey);
            emf.RecordSaved += emf_RecordSaved;

            (this.Record.Form.Project as CDC.SampleShell.Domain.Project).Legacy_RecordLocked(Record.GlobalRecordId.ToString()); // hack!

            emf.ShowDialog();

            (this.Record.Form.Project as CDC.SampleShell.Domain.Project).Legacy_RecordUnlocked(Record.GlobalRecordId.ToString()); // hack!

            emf.RecordSaved -= emf_RecordSaved;
            emf.Dispose();
            emf = null;
        }

        private void emf_RecordSaved(object sender, SaveRecordEventArgs e)
        {
            if (RecordSaved != null)
            {
                RecordSaved(this, new RecordSavedEventArgs((int)(Record.GetData("UniqueKey")), Record.GlobalRecordId, false));

                foreach (string fieldName in Record.FieldNames)
                {
                    RaisePropertyChanged(fieldName);
                }

                (this.Record.Form.Project as CDC.SampleShell.Domain.Project).Legacy_RecordUpdated(e.RecordGuid); // hack!
            }
        }

        public bool ContainsSearchString(string searchString)
        {
            if (String.IsNullOrEmpty(searchString)) return true;

            if (searchString.Contains("=")) // search based on field names if field names are provided
            {
                string[] split1 = searchString.Split(' '); // e.g. string is "ID=101 Status=Alive" so a simple split breaks this into two chunks

                bool[] trueParams = new bool[split1.Length]; // determining which params are true/false

                for (int i = 0; i < trueParams.Length; i++)
                {
                    trueParams[i] = false;
                }

                for (int i = 0; i < split1.Length; i++)
                {
                    string parameterLevel1 = split1[i];

                    if (!parameterLevel1.Contains('='))
                    {
                        trueParams[i] = true;
                        continue;
                    }

                    string[] split2 = parameterLevel1.Split('=');

                    if (split2.Length == 2)
                    {
                        bool isValidField = Record.ContainsField(split2[0]);

                        if (isValidField)
                        {
                            string searchValue = split2[1];
                            string dataValue = Record.GetData(split2[0]).ToString();

                            if (searchValue.Equals(dataValue, StringComparison.OrdinalIgnoreCase))
                            {
                                trueParams[i] = true;
                            }
                            else if (searchValue.EndsWith("*"))
                            {
                                string wildcardFreeSearchValue = searchValue.Replace("*", String.Empty);

                                if (wildcardFreeSearchValue.Length <= dataValue.Length)
                                {
                                    trueParams[i] = dataValue.Substring(0, wildcardFreeSearchValue.Length).Equals(wildcardFreeSearchValue, StringComparison.OrdinalIgnoreCase);
                                }
                            }
                        }
                    }
                }

                foreach (bool parameter in trueParams)
                {
                    if (parameter == false)
                    {
                        return false;
                    }
                }

                return true;
            }
            else // do Google-style search if the user provides no field names or operators
            {
                foreach (string fieldName in Record.FieldNames)
                {
                    string fieldValue = Record.GetData(fieldName).ToString();
                    if (fieldValue.Equals(searchString, StringComparison.OrdinalIgnoreCase))
                    {
                        return true;
                    }
                    else if (fieldValue.Contains(searchString, StringComparison.OrdinalIgnoreCase))
                    {
                        return true;
                    }
                }
            }

            return false;
        }
    }
}
